package com.example.myapplication

import android.Manifest
import android.annotation.SuppressLint
import android.content.*
import android.content.pm.PackageManager
import android.graphics.Point
import android.location.Address
import android.location.Geocoder
import android.location.Location
import android.net.Uri
import android.os.Bundle
import android.os.IBinder
import android.provider.Settings
import android.util.Log
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.lifecycle.ViewModelProvider
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.recyclerview.widget.LinearLayoutManager
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.viewholder.BaseViewHolder
import com.example.myapplication.databinding.ActivityMapsBinding
import com.example.myapplication.geofence.ForegroundGeofenceLocationServiceConnection
import com.example.myapplication.geofence.GeofenceLocationService
import com.example.myapplication.geofence.GeofenceManager
import com.example.myapplication.geolocation.GeoLocationCallback
import com.example.myapplication.geolocation.GoogleGeolocation
import com.example.myapplication.geolocation.IBaseGeoLocation
import com.google.android.gms.common.api.ApiException
import com.google.android.gms.location.GeofenceStatusCodes
import com.google.android.gms.maps.*
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.LatLngBounds
import com.google.android.material.snackbar.Snackbar
import com.google.maps.android.SphericalUtil
import kotlin.math.round


class MapsActivity : AppCompatActivity(), OnMapReadyCallback, GeoLocationCallback {
    private val TAG = AppCompatActivity::class.java.simpleName

    companion object {
        const val MAX_RADIUS = 1000
        const val MIN_RADIUS = 150
    }

    lateinit var mGeofenceManager: GeofenceManager

    lateinit var mGeoLocation: IBaseGeoLocation

    private var mMap: GoogleMap? = null
    private lateinit var binding: ActivityMapsBinding

    lateinit var adapter: BaseQuickAdapter<Address, BaseViewHolder>
    private lateinit var viewModel: MapsViewModel
    private val geocoder: Geocoder by lazy {
        Geocoder(this)
    }

    override
    fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMapsBinding.inflate(layoutInflater)
        setContentView(binding.root)
        val mapFragment = supportFragmentManager
            .findFragmentById(R.id.map) as SupportMapFragment
        mapFragment.getMapAsync(this)
        viewModel = ViewModelProvider(this).get(MapsViewModel::class.java)
        initViews()
        initGeolocation()
        initGeofencingClient()
        initClick()
    }

    private fun initGeolocation() {
        mGeoLocation = GoogleGeolocation.instance.initLocationClient(this)
    }

    private fun initGeofencingClient() {
        mGeofenceManager = GeofenceManager.newInstance().initGeofencingClient(this)
    }


    private fun initViews() {
        initRv()
        viewModel.placesInfoLiveData.observe(this) {
            binding.searchEdt.text.clear()
            binding.searchEdt.hint = it
        }
        viewModel.placesListLiveData.observe(this) {
            adapter.setList(it)
            if (it.isNotEmpty()) {
                binding.locationRv.visibility = View.VISIBLE
                Log.i(TAG, "${it.size} ${it[0].getAddressLine(0)}")
            } else {
                binding.locationRv.visibility = View.GONE
                Toast.makeText(this, "empty", Toast.LENGTH_SHORT).show()
            }
        }
    }

    override fun onMapReady(googleMap: GoogleMap) {
        mMap = googleMap
        mMap?.uiSettings?.isMyLocationButtonEnabled = false
        showCircleView()
        initCameraListener()
        enableMyLocation()
    }

    private fun initClick() {
        binding.locationBtn.setOnClickListener {
            if (!checkPermissions()) {
                enableMyLocation()
                return@setOnClickListener
            }
            mGeoLocation.onStartLocation(this, this)
            currentLocation?.let {
                mMap?.moveCamera(CameraUpdateFactory.newLatLng(LatLng(it.latitude, it.longitude)))
            }

        }
        binding.addBtn.setOnClickListener {
            if (!checkPermissions()) {
                enableMyLocation()
                return@setOnClickListener
            }
            mMap?.animateCamera(CameraUpdateFactory.zoomIn());
        }
        binding.subtractBtn.setOnClickListener {
            if (!checkPermissions()) {
                enableMyLocation()
                return@setOnClickListener
            }
            mMap?.animateCamera(CameraUpdateFactory.zoomOut());
        }
        findViewById<TextView>(R.id.tv_add).setOnClickListener {
            if (!checkPermissions()) {
                enableMyLocation()
                return@setOnClickListener
            }
            mMap?.let {
                addGeofenceDataReq(it.cameraPosition.target, radiusDistance.toFloat())
            }
        }
        binding.searchEdt.setOnEditorActionListener { v, id, keyEvent ->
            if (id == EditorInfo.IME_ACTION_SEARCH) {
                viewModel.reqPlacesInfoFromName(geocoder, binding.searchEdt.text.trim().toString())
                true
            }
            false
        }
    }

    private fun initRv() {
        adapter = object : BaseQuickAdapter<Address, BaseViewHolder>(R.layout.item_location_info_view) {
            override fun convert(holder: BaseViewHolder, item: Address) {
                holder.setText(R.id.locationNameTv, item.getAddressLine(0))
            }
        }
        adapter.setOnItemClickListener { mAdapter, view, position ->
            binding.locationRv.visibility = View.INVISIBLE
            var data = adapter.getItem(position)
            mMap?.animateCamera(CameraUpdateFactory.newLatLng(LatLng(data.latitude, data.longitude)))
            hideKeyboard()
        }
        binding.locationRv.adapter = adapter
        binding.locationRv.layoutManager = LinearLayoutManager(this)
    }

    private fun enableMyLocation() {
        permissionResultLauncher.launch(
            arrayOf(
                Manifest.permission.ACCESS_FINE_LOCATION,
                Manifest.permission.ACCESS_COARSE_LOCATION,
                Manifest.permission.ACCESS_BACKGROUND_LOCATION
            )
        )

    }

    private fun checkPermissions(): Boolean {
        return ActivityCompat.checkSelfPermission(
            this,
            Manifest.permission.ACCESS_COARSE_LOCATION
        ) == PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
            this,
            Manifest.permission.ACCESS_FINE_LOCATION
        ) == PackageManager.PERMISSION_GRANTED
    }

    var keyCount = 1
    public fun addGeofenceDataReq(center: LatLng, radius: Float) {
        mGeofenceManager.startGeofencing(
            this,
            mGeofenceManager.getGeofencing(
                center.latitude,
                center.longitude,
                radius,
                "key+${keyCount++}",
                if (binding.radioGroup.checkedRadioButtonId == R.id.arriveBtn) GeofenceManager.GEOFENCE_TRANSITION_ENTER else GeofenceManager.GEOFENCE_TRANSITION_EXIT
            )
        )?.run {
            addOnSuccessListener {
                Toast.makeText(this@MapsActivity, "add Geofence key ${keyCount - 1} radius $radius  success", Toast.LENGTH_SHORT).show()
            }
            addOnFailureListener {
                Log.e(TAG, "addOnFailureListener")
                it.printStackTrace()
                if (it is ApiException) {
                    when (it.statusCode) {
                        GeofenceStatusCodes.GEOFENCE_NOT_AVAILABLE -> {
                            Log.e(
                                TAG,
                                "Geofence service is not available now. Typically this is because the user turned off location access in settings > location access."
                            )
                        }
                        GeofenceStatusCodes.GEOFENCE_TOO_MANY_GEOFENCES -> {
                            Log.e(TAG, "Your app has registered more than 100 geofences. Remove unused ones before adding new geofences.")
                        }
                        GeofenceStatusCodes.GEOFENCE_TOO_MANY_PENDING_INTENTS -> {
                            Log.e(TAG, "You have provided more than 5 different PendingIntents to the")
                        }
                    }
                }
            }
        }
    }

    private fun showCircleView() {
        binding.circleGroup.visibility = View.VISIBLE
        binding.circleGroup.post {
            mMap?.let {
                makeCircleZoomState(it.cameraPosition.target, MIN_RADIUS.toDouble(), true, false)
            }
        }
    }

    var radiusDistance = MIN_RADIUS
    private fun setDistance() {
        mMap?.let {
            radiusDistance =
                round(SphericalUtil.computeDistanceBetween(it.cameraPosition.target, getMapCircleLeftPoint())).toInt()
            if (radiusDistance > MAX_RADIUS) radiusDistance = MAX_RADIUS
            if (radiusDistance < MIN_RADIUS) radiusDistance = MIN_RADIUS
            binding.distance.text = "${radiusDistance}m"
            Log.i(
                TAG,
                "distance =${
                    SphericalUtil.computeDistanceBetween(
                        it.cameraPosition.target,
                        getMapCircleLeftPoint()
                    )
                } minzoom=${mMap?.minZoomLevel} maxzoom=${mMap?.maxZoomLevel} zoom=${mMap?.cameraPosition?.zoom}= "
            )
        }
    }

    var currentLocation: Location? = null
    var firstMoveToCurrent = true

    @SuppressLint("MissingPermission")
    override fun onLocationChanged(location: Location) {
        Log.i(TAG, "onLocationChanged $location.latitude - $location.longitude")
        if (mMap?.isMyLocationEnabled == false) {
            mMap?.isMyLocationEnabled = true
        }
        if (firstMoveToCurrent) {
            firstMoveToCurrent = false
            mMap?.moveCamera(CameraUpdateFactory.newLatLng(LatLng(location.latitude, location.longitude)))
        }
        currentLocation = location

    }


    private fun initCameraListener() {
        mMap?.setOnCameraMoveListener {
            Log.i(TAG, "setOnCameraMoveListener")
            setDistance()
        }
        mMap?.setOnCameraIdleListener {
            Log.i(TAG, "setOnCameraIdleListener")
            mMap?.let {
                viewModel.reqPlacesInfoFromLatlng(geocoder, it.cameraPosition.target.latitude, it.cameraPosition.target.longitude)
                //                reqPlacesInfoFromLatlng(it.cameraPosition.target.latitude, it.cameraPosition.target.longitude)
                makeCircleZoomState(
                    it.cameraPosition.target,
                    SphericalUtil.computeDistanceBetween(it.cameraPosition.target, getMapCircleLeftPoint()),
                    false
                )
            }
        }
        mMap?.setOnCameraMoveCanceledListener {
            Log.i(TAG, "setOnCameraMoveCanceledListener")

        }
        mMap?.setOnCameraMoveStartedListener {
            Log.i(TAG, "setOnCameraMoveCanceledListener")

        }
    }

    private fun getMapCircleLeftPoint(): LatLng {
        val x = (binding.circleIv.right)
        val y = (binding.circleIv.bottom - binding.circleIv.height / 2 - binding.typeBg.bottom)
        val projection: Projection = mMap?.projection!!
        return projection.fromScreenLocation(Point(x, y))
    }

    private fun makeCircleZoomState(center: LatLng, distance: Double, defaultRefreshCamera: Boolean, needAnimate: Boolean? = true) {
        var targetDistance: Double = distance
        var reset = false
        if (round(distance) < MIN_RADIUS) {
            targetDistance = MIN_RADIUS.toDouble()
            reset = true
        } else if (round(distance) > MAX_RADIUS) {
            targetDistance = MAX_RADIUS.toDouble()
            reset = true
        }
        if (reset || defaultRefreshCamera) {
            Log.i(
                TAG,
                "makeCircleZoomState  distance=${distance}-${targetDistance} reset=${reset} defaultRefreshCamera=${defaultRefreshCamera} needAnimate=${needAnimate}"
            )
            val australiaBounds = LatLngBounds( // SW bounds
                SphericalUtil.computeOffset(center, targetDistance, 270.0),
                SphericalUtil.computeOffset(center, targetDistance, 90.0)
            )
            if (needAnimate == true) {
                mMap?.animateCamera(CameraUpdateFactory.newLatLngBounds(australiaBounds, (binding.root.width - binding.circleIv.width) / 2));
            } else {
                mMap?.moveCamera(CameraUpdateFactory.newLatLngBounds(australiaBounds, (binding.root.width - binding.circleIv.width) / 2));
            }
        }

    }

    var myLocationReceiver: BroadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            val location: Location? = intent?.getParcelableExtra(GeofenceLocationService.EXTRA_LOCATION)
            if (location != null) {
                currentLocation = location
                Log.i(TAG, "${location.latitude}")
            }
        }

    }

    override fun onResume() {
        super.onResume()
        LocalBroadcastManager.getInstance(this).registerReceiver(
            myLocationReceiver,
            IntentFilter(GeofenceLocationService.ACTION_BROADCAST)
        );
    }

    override fun onPause() {
        super.onPause()
        LocalBroadcastManager.getInstance(this).unregisterReceiver(myLocationReceiver);
    }

    override fun onStart() {
        super.onStart()
        bindService(
            Intent(this, GeofenceLocationService::class.java), mServiceConnection,
            BIND_AUTO_CREATE
        )
        mServiceConnection.service?.startLocationUpdates()
        mGeoLocation.onStartLocation(this, this)
    }

    override fun onStop() {
        if (mBound) {
            unbindService(mServiceConnection);
            mBound = false;
        }
        super.onStop()
        mGeoLocation.onStopLocation(this)
    }

    @SuppressLint("MissingPermission")
    var permissionResultLauncher: ActivityResultLauncher<Array<String>> =
        registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
            var allPass = true
            for ((key, value) in it.entries) {
                if (!value) {
                    allPass = false
                }
            }
            if (allPass) {
                mMap?.isMyLocationEnabled = true
                mGeoLocation.onStartLocation(this, this)
            } else {
                showSnackbar(
                    R.string.permission_denied_explanation, R.string.settings
                ) { // Build intent that displays the App settings screen.
                    val intent = Intent()
                    intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
                    val uri = Uri.fromParts(
                        "package",
                        BuildConfig.APPLICATION_ID, null
                    )
                    intent.data = uri
                    intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
                    startActivity(intent)
                }

            }

        }


    private fun showSnackbar(
        mainTextStringId: Int, actionStringId: Int,
        listener: View.OnClickListener
    ) {
        Snackbar.make(
            findViewById(android.R.id.content),
            getString(mainTextStringId),
            Snackbar.LENGTH_INDEFINITE
        )
            .setAction(getString(actionStringId), listener).show()
    }


    private var mBound = false
    private val mServiceConnection = object : ForegroundGeofenceLocationServiceConnection() {
        override fun onServiceConnected(name: ComponentName, service: IBinder) {
            super.onServiceConnected(name, service)
            mBound = true
        }

        override fun onServiceDisconnected(name: ComponentName) {
            super.onServiceDisconnected(name)
            mBound = false
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        //        mServiceConnection.service?.stopAllGeofencing()
    }

    private fun hideKeyboard(): Boolean {
        val view = window.peekDecorView()
        val imm = this.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
        return imm.hideSoftInputFromWindow(view.windowToken, InputMethodManager.RESULT_UNCHANGED_SHOWN)
    }
}
